package cn.clq.ymcc.service.impl;

import cn.clq.ymcc.constant.PayOrderStateConstants;
import cn.clq.ymcc.domain.AlipayInfo;
import cn.clq.ymcc.domain.PayOrder;
import cn.clq.ymcc.dto.AlipayNotifyDto;
import cn.clq.ymcc.dto.PayApplyDto;
import cn.clq.ymcc.dto.PayNotifyDto;
import cn.clq.ymcc.exception.GlobalException;
import cn.clq.ymcc.mapper.PayOrderMapper;
import cn.clq.ymcc.service.IAlipayInfoService;
import cn.clq.ymcc.service.IPayOrderService;
import cn.clq.ymcc.util.AssertUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.alipay.easysdk.kernel.util.ResponseChecker;
import com.alipay.easysdk.payment.common.models.AlipayTradeCloseResponse;
import com.alipay.easysdk.payment.common.models.AlipayTradeRefundResponse;
import com.alipay.easysdk.payment.page.Client;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.SendStatus;
import org.apache.rocketmq.client.producer.TransactionSendResult;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Date;
import java.util.Map;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author stone.chen
 * @since 2022-07-31
 */
@Service
@Slf4j
public class PayOrderServiceImpl extends ServiceImpl<PayOrderMapper, PayOrder> implements IPayOrderService {

    @Autowired
    private IAlipayInfoService alipayInfoService;

    @Autowired
    private RocketMQTemplate rocketMQTemplate;


    /**
     * @description: 根据订单号查询支付单是否已经存在，即消费者是否已经消费了消息
     * @param: orderNo
     * @return: cn.clq.ymcc.domain.PayOrder
     * @author clqlongbao
     * @date: 2022/8/1 11:16
     */
    @Override
    public PayOrder selectByOrderNo(String orderNo) {
        Wrapper<PayOrder> wrapper = new EntityWrapper<>();
        wrapper.eq("order_no",orderNo);
        return selectOne(wrapper);
    }


    /**
     * @description:  发起支付请求，返回登陆支付宝的form表单字符串
     * @param: dto
     * @return: cn.clq.ymcc.result.JSONResult
     * @author clqlongbao
     * @date: 2022/8/1 22:18
     */
    @Override
    public String apply(PayApplyDto dto) {
        //1.校验支付单是否存在，即消费者是否消费了消息
        PayOrder payOrder = selectByOrderNo(dto.getOrderNo());
        AssertUtil.isNotNull(payOrder,"消费者还未消费消息，支付单不存在");
        //2.校验支付单的状态是否为待支付
        Integer payStatus = payOrder.getPayStatus();
        AssertUtil.isTrue(payStatus == PayOrderStateConstants.STATE_WAIT_PAY,"支付单状态异常，支付失败");
        if(payOrder.getPayType() == 1){//支付宝支付
            //以上消息都确认无误的话，就调用支付宝的支付接口
            return alipay(payOrder,dto);
        } else if (payOrder.getPayType() == 2) {//微信支付
        }
        return null;
    }



    /**
     * @description: 调用支付宝的支付接口，返回用户登录的form表单
     * @param: payOrder dto
     * @return: java.lang.String
     * @author clqlongbao
     * @date: 2022/8/1 22:35
     */
    private String alipay(PayOrder payOrder, PayApplyDto dto) {
        // 1. 设置全局参数,从数据库的配置表拿数据（全局只需设置一次）
        AlipayInfo alipayInfo = alipayInfoService.selectOne(null);
        Factory.setOptions(getOptions(alipayInfo));
        // 2. 网页端支付
        try {
            Client page = Factory.Payment.Page();
            AlipayTradePagePayResponse response = page.pay(
                    payOrder.getSubject(),
                    payOrder.getOrderNo(),
                    payOrder.getAmount().toString(),
                    //同步回调地址,如果前端设置了回调地址就用前端的(直接返回到视频播放界面),前端没有设置的话，就跳转到订单支付成功的通知界面
                    StringUtils.isEmpty(dto.getCallUrl())?alipayInfo.getReturnUrl():dto.getCallUrl()
            );
            System.out.println(response.getBody());//拿到的就是用户登录的form表单

            // 3. 处理响应或异常
            if (ResponseChecker.success(response)) {
                System.out.println("调用成功");
                return response.getBody();
            } else {
                System.err.println("调用失败，原因：");
                throw new GlobalException("支付调用失败！！！");
            }
        } catch (Exception e) {
            System.err.println("调用遭遇异常，原因：" + e.getMessage());
            throw new RuntimeException(e.getMessage(), e);
        }
    }

    /**
     * @description: 全局参数配置
     * @param:
     * @return: com.alipay.easysdk.kernel.Config
     * @author clqlongbao
     * @date: 2022/8/1 22:51
     */
    private static Config getOptions(AlipayInfo alipayInfo) {
        Config config = new Config();
        config.protocol = alipayInfo.getProtocol();
        config.gatewayHost = alipayInfo.getGatewayHost();//沙箱API地址
        config.signType = alipayInfo.getSignType();
        config.appId = alipayInfo.getAppId();
        // 为避免私钥随源码泄露，推荐从文件中读取私钥字符串而不是写入源码中
        config.merchantPrivateKey = alipayInfo.getMerchantPrivateKey();
        //注：如果采用非证书模式，则无需赋值上面的三个证书路径，改为赋值如下的支付宝公钥字符串即可
        config.alipayPublicKey = alipayInfo.getAlipayPublicKey();
        //可设置异步通知接收服务地址（可选）
        config.notifyUrl = alipayInfo.getNotifyUrl();
        //可设置AES密钥，调用AES加解密相关接口时需要（可选）
        config.encryptKey = "";
        return config;
    }



    /**
     * @description: 支付包支付成功之后，异步回调处理业务
     * 1.修改支付单状态
     * 2.发送事务消息，修改订单状态和修改用户观看视频的权限
     * @param: dto
     * @return: java.lang.String
     * @author clqlongbao
     * @date: 2022/8/3 23:24
     */
    @Override
    public String notify(AlipayNotifyDto dto) throws Exception {
        //1.验签
        String jsonString = JSON.toJSONString(dto);
        Map<String,String> dtoMap = JSONObject.parseObject(jsonString, Map.class);
        Boolean isValid = Factory.Payment.Common().verifyNotify(dtoMap);
        //1.1校验支付宝的回调信息是否是真实的，防止中途被篡改了
        if(!isValid){
            return "Fail";
        }
        //1.2校验是否是我们的订单
        PayOrder payOrder = selectByOrderNo(dto.getOut_trade_no());
        if(payOrder== null){
            return "Fail";
        }
        //1.3校验支付宝返回的订单状态是否是成功
        String trade_status = dto.getTrade_status();
        if(!trade_status.equals(AlipayNotifyDto.TRADE_SUCCESS)){
            return "Fail";
        }
        //1.4校验支付单状态是否是取消
        if(payOrder.getPayStatus() == PayOrderStateConstants.STATE_CANCEL){
            //如果支付单已经被取消，但是支付宝支付成功的消息回来了
            try {
                log.info("调用支付宝退款！！");
                //调用退款
                AlipayTradeRefundResponse refund = Factory.Payment.Common()
                        .refund(payOrder.getOrderNo(), payOrder.getAmount().toString());
            } catch (Exception e) {
                e.printStackTrace();
                /**
                 * 做兜底
                 * 1.保存记录到数据库
                 * 2.运维人员更进
                 */
            }
            return "success";
        }
        //1.5校验支付单状态是否是待支付
        if(payOrder.getPayStatus() != PayOrderStateConstants.STATE_WAIT_PAY){
            return "Fail";
        }

        //处理支付成功之后的业务逻辑=============================
        //2.修改支付单状态
        payOrder.setPayStatus(PayOrderStateConstants.STATE_PAY_SUCCESS);
        payOrder.setUpdateTime(new Date());
        //updateById(payOrder); 应该在事务监听器中执行本地事务

        log.info("异步回调发送支付成功通知事务消息");
        //构建消息体
        PayNotifyDto payNotifyDto = new PayNotifyDto(
                payOrder.getOrderNo(),
                payOrder.getExtParams()
        );
        Message<String> message = MessageBuilder.withPayload(JSON.toJSONString(payNotifyDto)).build();
        TransactionSendResult transactionSendResult = rocketMQTemplate.sendMessageInTransaction(
                "service-pay-tx-group",
                "pay-notify-topic:pay-notify-tag",
                message,  //消息体
                payOrder   //额外参数
        );
        log.info("发送完支付异步通知事务消息--检查事务消息状态");
        LocalTransactionState localTransactionState = transactionSendResult.getLocalTransactionState();
        SendStatus sendStatus = transactionSendResult.getSendStatus();
        if(localTransactionState != LocalTransactionState.COMMIT_MESSAGE || sendStatus != SendStatus.SEND_OK ){
            log.info("校验发送事务消息状态异常");
            throw new GlobalException("处理支付成功结果失败！！");
            // TODO: 2022/8/3  记录日志：回调成功，但是事务消息发送异常
        }
        return "success";
    }


    /**
     * @description: 支付超时，取消支付单
     *  支付超时
     *      1.取消支付单
     *      2.调用支付宝，关闭支付
     *         。如果还在登录页面，是可以关闭交易的
     *         。如果已经到了支付页面，输入了支付密码，点击支付之后，那么就关闭不了
     *            --- 降级方案，在支付宝的异步通知中：走退款逻辑---
     * @param: orderNo
     * @return: void
     * @author clqlongbao
     * @date: 2022/8/4 3:04
     */
    @Override
    public void cancelOrder(String orderNo) {
        PayOrder payOrder = selectByOrderNo(orderNo);
        //取消支付单
        if(payOrder.getPayStatus().intValue() == PayOrderStateConstants.STATE_WAIT_PAY.intValue()){
            payOrder.setPayStatus(PayOrderStateConstants.STATE_CANCEL);
            updateById(payOrder);
        }
        //调用支付宝，关闭支付
        try {
            AlipayTradeCloseResponse close = Factory.Payment.Common().close(orderNo);
        } catch (Exception e) {
            e.printStackTrace();
            /**
             * 如果出错了要做兜底
             * 1.重试3次
             * 2.保存记录到数据库
             * 3.运维跟进
             */
        }
    }
}
